﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
using System.Threading;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;
using System.Collections.Specialized;

using Bling;
using Bling.DSL;
using Bling.Properties;
using Bling.Util;
using Bling.Animation;
using Bling.Linq;

namespace Bling.DSL {
  public partial interface BaseCoreValue<T> {
    BaseCustomAnimation MakeAnimation(Func<DoubleBl, DoubleBl> Tween);
  }
  public partial class CoreBl<T, BRAND> where BRAND : CoreBl<T, BRAND> {
    public BaseCustomAnimation MakeAnimation(Func<DoubleBl, DoubleBl> Tween) { return new CustomAnimation<T, BRAND>(Tween); }
  }
  public partial class DoubleBl {

    public class OutInBl {
      internal DoubleBl Target;
      internal Func<DoubleBl, DoubleBl> Out0;
      internal Func<DoubleBl, DoubleBl> In0;
      internal Func<DoubleBl, DoubleBl> InOut0;
      internal Func<DoubleBl, DoubleBl> OutIn0;
      internal OutInBl() {
        InOut0 = (t) => (t < 0.5).Condition(In0(t * 2) / 2d, .5 + Out0((t * 2) - 1) * .5);
        OutIn0 = (t) => (t < 0.5).Condition(Out0(t * 2) / 2d, .5 + In0((t * 2) - 1) * .5);
      }
      /// <summary>
      /// Decelerating from zero velocity.
      /// </summary>
      public DoubleBl Out { get { return Out0(Target); } }
      /// <summary>
      /// Accelerating from zero velocity.
      /// </summary>
      public DoubleBl In { get { return In0(Target); } }
      /// <summary>
      /// Acceleration until halfway, then deceleration.
      /// </summary>
      public DoubleBl InOut { get { return InOut0(Target); } }
      /// <summary>
      /// Deceleration until halfway, then acceleration.
      /// </summary>
      public DoubleBl OutIn { get { return OutIn0(Target); } }
      
    }
    public class EaseBl {
      internal DoubleBl Target;
      /// <summary>
      /// Easing equation function for an exponential (2^t) easing
      /// /// </summary>
      public OutInBl Expo {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => 1d - 2d.Bl().Pow(-10d * t),
            In0 = t => 2d.Bl().Pow(10d * (t - 1d)),
            InOut0 = t => {
              double d = 1;
              double c = 1;
              double b = 0;
              return ((t /= d / 2) < 1).Condition(
                 c / 2 * ( 2d.Bl().Pow( 10 * (t - 1)) + 0) + b,
                 c / 2 * (-2d.Bl().Pow(-10 * (t - 1)) + 2) + b);
            
            },
          };
        }
      }
      /// <summary>
      /// Easing equation function for a circular (sqrt(1-t^2)) easing
      /// </summary>
      public OutInBl Circ {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => (1d - (t - 1).Square()).Sqrt(),
            In0 =  t => - ((1d - t.Square()).Sqrt() - 1d),
            InOut0 = t => (t < 0.5).Condition(
              -.5 * ((1 - (2 * t).Square()).Sqrt() - 1d),
              .5 * ((1 - ((2 * t) - 2).Square()).Sqrt() + 1d)),
          };
        }
      }
      /// <summary>
      /// Easing equation function for a sinusoidal (sin(t)) easing  
      /// </summary>
      public OutInBl Sine {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => (t / 2d).ToPI().Sin,
            In0 = t => -1d * (t / 2d).ToPI().Cos + 1,
            InOut0 = t => {
              double d = 1;
              double c = 1;
              double b = 0;
              return ((t /= d / 2) < 1).Condition(
                +c / 2 * (((t - 0) / 2).ToPI().Sin - 0) + b,
                -c / 2 * (((t - 1) / 2).ToPI().Cos - 2) + b);
            },
          };
        }
      }

      /// <summary>
      /// Easing equation function for a quadratic (t^2). 
      /// </summary>
      public OutInBl Quad {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double c = 1, d = 1, b = 0;
              return -c * (t /= d) * (t - 2) + b;
            },
            In0 = t => {
              double c = 1, d = 1, b = 0;
              return c * (t /= d) * t + b;
            },
            InOut0 = t => {
              double c = 1, d = 1, b = 0;
              return ((t /= d / 2) < 1).Condition(
               +c / 2 * ((t - 0) * (t - 0) - 0) + b,
               -c / 2 * ((t - 1) * (t - 3) - 1) + b);
            },
          };
        }
      }

      /// <summary>
      /// Easing equation function for a cubic (t^3). 
      /// </summary>
      public OutInBl Cubic {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double c = 1, d = 1, b = 0;
              return c * ((t = t / d - 1).Pow(3) + 1) + b;
            },
            In0 = t => {
              double c = 1, d = 1, b = 0;
              return c * (t /= d).Pow(3) + b;
            },
            InOut0 = t => {
              double c = 1, d = 1, b = 0;
              return ((t /= d / 2) < 1).Condition(
                c / 2 * ((t - 0).Pow(3) + 0) + b,
                c / 2 * ((t - 2).Pow(3) + 2) + b);
            },
          };
        }
      }
      /// <summary>
      /// Easing equation function for a quatric (t^4). 
      /// </summary>
      public OutInBl Quatric {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double c = 1, d = 1, b = 0;
              return -c * ((t = t / d - 1).Pow(4) - 1) + b;
            },
            In0 = t => {
              double c = 1, d = 1, b = 0;
              return c * (t /= d).Pow(4) + b;
            },
            InOut0 = t => {
              double c = 1, d = 1, b = 0;
              return ((t /= d / 2) < 1).Condition(
                +c / 2 * ((t - 0).Pow(4) - 0) + b,
                -c / 2 * ((t - 2).Pow(4) - 2) + b);
            },
          };
        }
      }
      /// <summary>
      /// Easing equation function for a quintic (t^5). 
      /// </summary>
      public OutInBl Quintic {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double c = 1, d = 1, b = 0;
              return c * ((t = t / d - 1).Pow(5) + 1) + b;
            },
            In0 = t => {
              double c = 1, d = 1, b = 0;
              return c * (t /= d).Pow(5) + b;
            },
            InOut0 = t => {
              double c = 1, d = 1, b = 0;
              return ((t /= d / 2) < 1).Condition(
                c / 2 * ((t - 0).Pow(5) + 0) + b,
                c / 2 * ((t - 2).Pow(5) + 2) + b);
            },
          };
        }
      }


      /// <summary>
      /// Easing equation function for an elastic (exponentially decaying sine wave) easing out/in: 
      /// deceleration until halfway, then acceleration.
      /// </summary>
      public OutInBl Elastic {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double p = 1d * .3;
              double s = p / 4;
              return 2d.Bl().Pow(-10 * t) * ((t * 1d - s) * (2) / p).ToPI().Sin + 1d;
            },
            In0 = t => {
              double p = 1d * .3;
              double s = p / 4;
              return - (2d.Bl().Pow(10 * (t - 1)) * (((t - 1) * 1d - s) * 2 / p).ToPI().Sin + 0d);
            },
          };
        }
      }
      /// <summary>
      /// Easing equation function for a bounce (exponentially decaying parabolic bounce) easing out: 
      /// decelerating from zero velocity.
      /// </summary>
      public OutInBl Bounce {
        get {
          var result = new OutInBl() {
            Target = Target,
            Out0 = t => {
              var b0 = t < 1.0d / 2.75;
              var b1 = t < 2.0d / 2.75;
              var b2 = t < 2.5d / 2.75;
              return (t < 1.0d / 2.75).Condition(7.5625 * t.Square(),
                /**/ (t < 2.0d / 2.75).Condition(7.5625 * (t - (1.50 / 2.75)).Square() + .75,
                /**/ (t < 2.5d / 2.75).Condition(7.5625 * (t - (2.25 / 2.75)).Square() + .9375,
                /**********************/ 7.5625 * (t - (2.625 / 2.75)).Square() + .984375)));
            },
          };
          result.In0 = t => 1d - result.Out0(1d - t);
          return result;
        }
      }
      /// <summary>
      /// Easing equation function for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing in/out: 
      /// acceleration until halfway, then deceleration.
      /// </summary>
      public OutInBl Back {
        get {
          return new OutInBl() {
            Target = Target,
            Out0 = t => {
              double c = 1d;
              double d = 1d;
              double b = 0d;
              return c * ((t = t / d - 1) * t * ((1.70158 + 1) * t + 1.70158) + 1) + b;
            },
            In0 = t => {
              double c = 1d;
              double d = 1d;
              double b = 0d;
              return c * (t /= d) * t * ((1.70158 + 1) * t - 1.70158) + b;
            },
            InOut0 = t => {
              double c = 1d;
              double d = 1d;
              double b = 0d;

              double s = 1.70158;
              double s0 = s;
              double s1 = s;

              t /= d / 2;
              DoubleBl t0 = t;
              DoubleBl t1 = t;
              return (t < 1).Condition(
                c / 2 * (t0 * t0 * (((s0 *= (1.525)) + 1) * t0 - s0)) + b,
                c / 2 * ((t1 -= 2) * t1 * (((s1 *= (1.525)) + 1) * t1 + s1) + 2) + b);
            },
          };
        }
      }
    }
    public EaseBl Ease { get { return new EaseBl() { Target = this }; } }
  }
}

namespace Bling.Animation {
  public abstract class BaseCustomAnimation : AnimationTimeline {
    public abstract Func<DoubleBl, DoubleBl> Tween { set; }
    public abstract Expr From0 { set; }
    public abstract Expr   To0 { set; }
  }
  public class CustomAnimation<T,SELF> : BaseCustomAnimation where SELF : CoreBl<T,SELF> {
    protected static readonly GetProperty<CustomAnimation<T, SELF>, T> FromProperty = "From".NewProperty<CustomAnimation<T, SELF>, T>();
    protected static readonly GetProperty<CustomAnimation<T, SELF>, T> ToProperty = "To".NewProperty<CustomAnimation<T, SELF>, T>();
    private Func<double, T> CurrentValue;
    public override Func<DoubleBl, DoubleBl> Tween {
      set {
        CurrentValue = DefaultLinqEval.Eval<double, T>(Fraction => {
          Fraction = value == null ? Fraction : value(Fraction).Underlying;
          return From.LerpX(To, Fraction);
        });
      }
    }
    public CustomAnimation(Func<DoubleBl, DoubleBl> Tween) {
      this.Tween = Tween;
    }
    public CustomAnimation() : this(d => d) {}
    private CustomAnimation(Func<double, T> CurrentValue) {
      this.CurrentValue = CurrentValue;
    }
    public SELF From {
      get { return CoreBl<T, SELF>.ToBrand(FromProperty[this]); }
      set { From.Bind = value; }
    }
    public SELF To {
      get { return CoreBl<T,SELF>.ToBrand(ToProperty[this]); }
      set { To.Bind = value; }
    }
    public override Expr From0 {
      set { From.Now = CoreBl<T,SELF>.ToBrand((Expr<T>) value); }
    }
    public override Expr To0 {
      set { To.Now = CoreBl<T, SELF>.ToBrand((Expr<T>) value); }
    }

    public override Type TargetPropertyType {
      get { return typeof(T); }
    }
    protected override Freezable CreateInstanceCore() {
      var frozen = new CustomAnimation<T,SELF>(CurrentValue);
      if (!frozen.Duration.HasTimeSpan) frozen.Duration = new Duration(TimeSpan.FromSeconds(1));

      return frozen;
    }
    public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock) {
      var fraction = animationClock.CurrentTime.Value.TotalSeconds / Duration.TimeSpan.TotalSeconds;
      return CurrentValue(fraction);
    }
  }
  public interface CanStart {
    void Start();
    void Stop();
  }
  public class AnimatedCanvas : Canvas, CanStart {
    public static readonly GetProperty<AnimatedCanvas,double> AnimateProperty = "Animate".NewProperty<AnimatedCanvas, double>();
    public DoubleBl Animate {
      get { return AnimateProperty[this]; }
      set { Animate.Bind = value; }
    }
    public void Start() {
      Animate.Stop(false);
      Animate.Now = 0;
      Animate.Animate.Duration(1000).Forever().To = 1.0;
    }
    public void Stop() {
      Animate.Stop(true);
    }
  }


  internal class TimerTask : DependencyObject {
    public double Time; // can be safely read outside of the UI thread.
    public static GetProperty<TimerTask,double> TimeProperty = "Time".NewProperty<TimerTask, double>(0);
    public DoubleBl TimeSignal { get { return TimeProperty[this]; } }
    private Action OnZero;
    public TimerTask(BackgroundTimer owner, double initial) : this(owner, initial, () => { }) { }
    private readonly double Initial;
    public DoubleBl Scaled {
      get {
        return TimeSignal / Initial;
      }
    }

    public TimerTask(BackgroundTimer owner, double initial, Action OnZero) {
      Initial = initial;
      Time = initial;
      TimeSignal.Now = initial;
      owner.Tasks.Add(this);
      this.OnZero = OnZero;
    }
    public bool Update(double step) {
      Time = Math.Max(Time - step, 0);
      TimeSignal.Now = Time;
      if (Time == 0) {
        if (OnZero != null) OnZero();
        return true;
      } else return false;
    }
  }

  public class BackgroundTimer : CanStart {
    private readonly Dispatcher Dispatcher;
    private Timer timer;
    private readonly Action BackgroundTask;
    private readonly Action UITask;
    internal readonly List<TimerTask> Tasks = new List<TimerTask>();
    private bool busy = false;
    private readonly int Period;
    private int Step;
    private int Last;
    public BackgroundTimer(Dispatcher Dispatcher, int milliPeriod, Action BackgroundTask, Action UITask) {
      this.Dispatcher = Dispatcher;
      this.BackgroundTask = BackgroundTask;
      this.UITask = UITask;
      this.Period = milliPeriod;
      this.Step = Period;
      this.Last = (int) DateTime.Now.TimeOfDay.TotalMilliseconds;
    }
    public void Start() {
      timer = new Timer(new TimerCallback((x) => ((BackgroundTimer)x).BackgroundTask0()), this, Period, Period);
    }
    public void Stop() {
      if (timer != null) {
        timer.Dispose();
        timer = null;
      }
    }


    public DoubleBl Task(double millis) {
      var task = new TimerTask(this, millis);
      return task.Scaled;
    }
    public DoubleBl Task(double millis, Action onZero) {
      var task = new TimerTask(this, millis, onZero);
      return task.Scaled;
    }

    private delegate void DummyDelegate(BackgroundTimer timer);
    private void BackgroundTask0() {
      if (busy) return;
      busy = true;
      var now = (int) DateTime.Now.TimeOfDay.TotalMilliseconds;
      this.Step = now - Last;
      Last = now;

      BackgroundTask();
      Dispatcher.BeginInvoke(new DummyDelegate(timer => {
        if (timer.Tasks.Count > 0) { //  back in the UI thread!
          int i = 0;
          while (i < timer.Tasks.Count) {
            if (timer.Tasks[i].Update(timer.Step)) {
              timer.Tasks.RemoveAt(i);
            } else i++;
          }
        }
        timer.UITask();
        busy = false;
      }), this);
    }
  }
  public class SwapRenderer : DependencyObject {
    private RenderTargetBitmap bufB;
    public readonly int Width;
    public readonly int Height;
    public ImageSource Output() {
      return image.Source;
      //return image.Source.Map<ImageSource>(d => d);
    }
    public readonly Image image = new Image();
    public SwapRenderer(int width, int height, ShaderEffect effect) {
      this.Width = width;
      this.Height = height;
      var bufA = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
      bufB = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
      image.Source = bufA;
      image.Measure(new Size(Width, Height));
      image.Arrange(new Rect(0, 0, Width, Height));
      image.Effect = effect;
    }
    public void Swap(Visual visual) {
      var bufA = (RenderTargetBitmap)image.Source;
      image.Source = bufB;
      if (visual != null) bufB.Render(visual);

      image.Measure(new Size(Width, Height));
      image.Arrange(new Rect(0, 0, Width, Height));
      bufA.Render(image);
      bufB = bufA;
    }
    private DispatcherTimer timer;
    public void Go(long millis, Visual visual) {
      timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(millis) };
      timer.Tick += (x, y) => Swap(visual);
      timer.Start();
    }
  }

}

